package mspbots.cw.business.core.tickets;

import com.fasterxml.jackson.databind.JsonNode;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import mspbots.cw.business.core.BaseReceiver;
import mspbots.cw.business.core.boards.BoardAssociationsRepository;
import mspbots.cw.business.core.users.Users;
import mspbots.cw.business.core.users.UsersRepository;
import mspbots.cw.common.ResultMsg;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.BeanUtils;
import org.springframework.data.relational.core.query.Criteria;
import org.springframework.data.relational.core.query.Query;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * com.mspbots.core.amqp.receivers.TicketsReceiver
 *
 * @author Alex bob(https://github.com/vnobo)
 * @date Created by 2019/8/12
 */
@Log4j2
@Component
@RequiredArgsConstructor
public class TicketsReceiver extends BaseReceiver {

    private final TicketsRepository ticketsRepository;
    private final TicketTaskRepository ticketTaskRepository;
    private final TicketsFlowRepository ticketsFlowRepository;
    private final UsersRepository usersRepository;
    private final BoardAssociationsRepository boardAssociationsRepository;

    @RabbitListener(queues = "connect-wise.tickets.tl")
    public void processMessage(ResultMsg resultMsg) {
        Tickets data = processTicket(resultMsg);


        TicketsFlow ticketFlow = TicketsFlow.builder().build();
        BeanUtils.copyProperties(data, ticketFlow);

        splicLink(resultMsg, data);
        saveTicket(resultMsg, data);
        saveTicketFlow(resultMsg, ticketFlow);
    }

    private void splicLink(ResultMsg resultMsg, Tickets data) {
        String url = this.r2dbcEntityTemplate.select(SysDictDataEntity.class)
                .matching(Query.query(Criteria.where("tenantCode").is(resultMsg.getTenantId())
                        .and("subjectCode").is("CW")
                        .and("dictLabel").is("url")))
                .one().map(SysDictDataEntity::getDictValue).block(Duration.ofSeconds(10));
        if (!ObjectUtils.isEmpty(url)) {
            String ticketUrl = "https://" + url + "/v4_6_release/ConnectWise.aspx?locale=en_US&routeTo=ServiceFV&recid=" + data.getTicketId();
            data.setLink(ticketUrl);
        }
    }

    private void saveTicket(ResultMsg resultMsg, Tickets data) {
        if ("deleted".equals(resultMsg.getAction())) {
            this.r2dbcEntityTemplate.delete(Tickets.class)
                    .matching(Query.query(Criteria.where("tenantCode").is(resultMsg.getTenantId())
                            .and("ticketId").is(data.getTicketId())))
                    .all().block(Duration.ofSeconds(10));
            return;
        }


        boolean flag = false;
        Tickets dbData = this.ticketsRepository.findByTicketIdAndTenantCode(resultMsg.getId(), resultMsg.getTenantId()).block(Duration.ofSeconds(10));
        if (ObjectUtils.isEmpty(dbData)) {
            this.ticketsRepository.save(data).block(Duration.ofSeconds(10));
            flag = true;
        } else if (ObjectUtils.isEmpty(dbData.getUpdatedTime()) || ObjectUtils.isEmpty(data.getUpdatedTime()) || dbData.getUpdatedTime().isBefore(data.getUpdatedTime())) {
            data.setId(dbData.getId());
            this.ticketsRepository.save(data).block(Duration.ofSeconds(10));
            flag = true;

        }

        if (flag) {
            super.messagingTemplate.convertAndSend("bot.task.cw", "bot.cw.ticket", data);
        }
    }

    private void saveTicketFlow(ResultMsg resultMsg, TicketsFlow data) {
        if (ObjectUtils.isEmpty(data.getUpdatedTime())) {
            log.info("tenantCode:{}, ticketId:{}", data.getTenantCode(), data.getTicketId());
        }
        if ("deleted".equals(resultMsg.getAction())) {
            TicketsFlow flow = this.ticketsFlowRepository.findFlowByTicketIdAndTenantCode(data.getTicketId(), resultMsg.getTenantId()).block(Duration.ofSeconds(10));
            data.setFromStatus(ObjectUtils.isEmpty(flow) ? "none" : flow.getStatusName());
            this.ticketsFlowRepository.save(data).block(Duration.ofSeconds(10));
            return;
        }

        TicketsFlow flow1 = this.databaseClient.sql("SELECT id FROM task_cw_ticket_flow " +
                "WHERE tenant_code =:tenantCode AND ticket_id =:ticketId AND updated_time =:updatedTime limit 1")
                .bind("tenantCode", data.getTenantCode())
                .bind("ticketId", data.getTicketId())
                .bind("updatedTime", data.getUpdatedTime())
                .map(row->{
                    TicketsFlow flow=  new TicketsFlow();
                    flow.setId(row.get(0,Long.class));
                    return flow;
                }).one().block(Duration.ofSeconds(10));
        if (ObjectUtils.isEmpty(flow1)) {
            TicketsFlow flow = this.ticketsFlowRepository.findFlowByTicketIdAndTenantCode(data.getTicketId(), resultMsg.getTenantId()).block(Duration.ofSeconds(10));
            data.setFromStatus(ObjectUtils.isEmpty(flow) ? "none" : flow.getStatusName());
            this.ticketsFlowRepository.save(data).block(Duration.ofSeconds(10));
        }
    }


    private Tickets processTicket(ResultMsg resultMsg) {
        JsonNode entity = resultMsg.getEntity();
        Tickets data = super.objectMapper.convertValue(resultMsg.getEntity(), Tickets.class);
        data.setId(null);
        data.setTicketId(resultMsg.getId());
        data.setTenantCode(resultMsg.getTenantId());
        data.setSyncTime(resultMsg.getSyncDate());
        data.setAction("none".equals(resultMsg.getAction()) ? "added" : resultMsg.getAction());
        data.setConsumeTime(LocalDateTime.now());

        if ("deleted".equals(resultMsg.getAction())) {
            return data;
        }
        data.setRecordType(ObjectUtils.isEmpty(entity.get("project")) ? data.getRecordType() : "ProjectTicket");
        data.setStatusName(super.getStringValueName(entity.get("status")));
        data.setContactName(super.getStringValueName(entity.get("contact")));
        data.setSlaId(super.getLongValueId(entity.get("sla")));
        data.setSlaName(super.getStringValueName(entity.get("sla")));
        data.setPriorityId(super.getLongValueId(entity.get("priority")));
        data.setPriorityName(super.getStringValueName(entity.get("priority")));
        data.setCompanyId(super.getLongValueId(entity.get("company")));
        data.setCompanyName(super.getStringValueName(entity.get("company")));
        data.setUpdatedBy(super.getStringValue(entity.findValue("updatedBy")));
        JsonNode lastUpdated = entity.get("_info").get("lastUpdated");

        JsonNode updateTime = ObjectUtils.isEmpty(lastUpdated) ? entity.get("updatedTime") : lastUpdated;

        data.setUpdatedTime(super.getDateTimeValue(updateTime));
        data.setBoardId(super.getLongValueId(entity.get("board")));
        data.setBoardName(super.getStringValueName(entity.get("board")));
        data.setTypeName(super.getStringValueName(entity.get("type")));
        data.setSubTypeName(super.getStringValueName(entity.get("subType")));
        data.setDateEntered(super.getDateTimeValue(entity.findValue("dateEntered")));
        data.setAgreementName(super.getStringValueName(entity.get("agreement")));
        data.setAgreementType(ObjectUtils.isEmpty(entity.get("agreement")) ? null :
                (ObjectUtils.isEmpty(entity.get("agreement").get("type")) ? null : entity.get("agreement").get("type").asText()));
        data.setHasConfigurations(!ObjectUtils.isEmpty(entity.get("configurations")) && entity.get("configurations").asBoolean());
        data.setNoteResolutionFlag(!ObjectUtils.isEmpty(entity.get("resolutionFlag")) && entity.get("resolutionFlag").asBoolean());
        data.setOwnerIdentifier(super.getStringValueIdentifier(entity.get("owner")));
        data.setOwnerName(super.getStringValueName(entity.get("owner")));
        data.setSeverity(super.getStringValue(entity.get("severity")));
        data.setApproved(super.getBooleanValue(entity.get("approved")));
        data.setEnteredBy(ObjectUtils.isEmpty(entity.get("_info")) ? null : super.getStringValue(entity.get("_info").get("enteredBy")));
        data.setSourceId(super.getLongValueId(entity.get("source")));
        data.setSourceName(super.getStringValueName(entity.get("source")));
        data.setSiteId(super.getLongValueId(entity.get("site")));
        data.setSiteName(super.getStringValueName(entity.get("site")));
        data.setImpact(super.getStringValue(entity.get("impact")));
        data.setIsInSla(super.getBooleanValue(entity.get("isInSla")));
        data.setLocationId(super.getLongValueId(entity.get("location")));
        data.setLocationName(super.getStringValueName(entity.get("location")));
        data.setHasMergedChildTicketFlag(super.getBooleanValue(entity.get("hasMergedChildTicketFlag")));
        data.setLastUpdated(super.getDateTimeValue(entity.get("lastUpdated")));
        data.setServiceLocationId(super.getLongValueId(entity.get("serviceLocation")));
        data.setServiceLocationName(super.getStringValueName(entity.get("serviceLocation")));
        data.setTeamName(super.getStringValueName(entity.get("team")));
        data.setRequiredDate(super.getDateTimeValue(entity.get("requiredDate")));

        data.setItemId(super.getLongValueId(entity.get("item")));
        data.setItemName(super.getStringValueName(entity.get("item")));
        data.setResolveMinutes(super.getLongValue(entity.get("resolveMinutes")));


        data.setResources(super.getStringValue(entity.get("resources")));
        data.setDateResolved(super.getDateTimeValue(entity.get("dateResolved")));


        if (ObjectUtils.isEmpty(data.getSubTypeName())) {
            Long boardId = super.getLongValueId(entity.get("board"));
            Long typeId = super.getLongValueId(entity.get("type"));
            if (ObjectUtils.isEmpty(boardId) || ObjectUtils.isEmpty(typeId)) {
                data.setSubTypeName("none");
            } else {
                this.boardAssociationsRepository.findByBoardIdAndTypeIdAndTenantCode(boardId, typeId, resultMsg.getTenantId())
                        .map(count -> count > 0)
                        .flatMap(countFlag -> {
                            if (countFlag) {
                                data.setSubTypeName("noSelect");
                            } else {
                                data.setSubTypeName("none");
                            }
                            return Mono.empty();
                        }).block(Duration.ofSeconds(10));
            }
        }

        Optional<Users> usersOpt = this.usersRepository.findByIdentifierAndTenantCode(data.getUpdatedBy(), data.getTenantCode())
                .blockOptional(Duration.ofSeconds(10));
        usersOpt.ifPresent(users -> data.setCwUid(users.getCwUid()));

        List<Long> childTicketIdList = new ArrayList<>();
        this.processTicketTask(resultMsg, childTicketIdList);

        if (!data.getNoteResolutionFlag() && !CollectionUtils.isEmpty(childTicketIdList)) {
            this.ticketsRepository.findByTicketIdAndTenantCodeOwner(data.getTenantCode(), childTicketIdList)
                    .map(count -> count > 0)
                    .flatMap(countFlag -> {
                        if (countFlag) {
                            data.setNoteResolutionFlag(true);
                        }
                        return Mono.empty();
                    }).block(Duration.ofSeconds(10));
        }

        return data;
    }

    private void processTicketTask(ResultMsg resultMsg, List<Long> childTicketIdList) {
        JsonNode tasksNode = resultMsg.getEntity().get("tasks");
        if (ObjectUtils.isEmpty(tasksNode) || tasksNode.isEmpty()) {
            return;
        }
        List<TicketTask> taskList = this.ticketTaskRepository.findByTicketIdAndTenantCode(resultMsg.getId(), resultMsg.getTenantId())
                .collectList().block(Duration.ofSeconds(10));
        Map<Long, TicketTask> taskMap = new HashMap<>();
        if (!CollectionUtils.isEmpty(taskList)){
            taskMap = taskList.parallelStream()
                    .collect(Collectors.toMap(TicketTask::getTaskId, Function.identity(), (key1, key2) -> key2));
        }

        Iterator<JsonNode> elements = tasksNode.elements();
        while (elements.hasNext()) {
            JsonNode next = elements.next();
            try {
                Long taskId = super.getLongValue(next.get("id"));
                TicketTask data = super.objectMapper.convertValue(next, TicketTask.class);
                data.setId(null);
                data.setTaskId(taskId);
                data.setTenantCode(resultMsg.getTenantId());
                data.setSyncTime(resultMsg.getSyncDate());
                data.setLastUpdated(super.getDateTimeValue(next.findValue("lastUpdated")));

                if (!ObjectUtils.isEmpty(next.get("childTicketId"))) {
                    childTicketIdList.add(super.getLongValue(next.get("childTicketId")));
                }

                if (taskMap.containsKey(taskId)){
                    data.setId(taskMap.get(taskId).getId());
                    taskMap.remove(taskId);
                }
                this.ticketTaskRepository.save(data).subscribe();

            } catch (IllegalArgumentException e) {
                log.error("------------ticket task parse error!!!---------data:{}", next);
                log.error(e.getMessage());
            }
        }
        if (!CollectionUtils.isEmpty(taskMap)){
            taskMap.forEach((key, value) -> this.ticketTaskRepository.deleteById(value.getId()).block(Duration.ofSeconds(10)));
        }
    }
}
